/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2007 OPeNDAP, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
/////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.IO;
using NDODS.DAP;
using NDODS.DAP.Exceptions;
using NDODS.DAP.Utils;

namespace NDODS.DAP.BaseTypes
{
    /// <summary> A <code>DStructure</code> in OPeNDAP can hold <em>N</em> instances of any of
    /// the other datatypes, including other structures.
    /// 
    /// </summary>
    /// <author>  jehamby
    /// </author>
    /// <version>  $Revision: 15901 $
    /// </version>
    /// <seealso cref="BaseType">
    /// </seealso>
    /// <seealso cref="DConstructor">
    /// </seealso>
    [Serializable]
    public class DStructure : DConstructor, IClientIO, ICloneable
    {
        /// <summary> Returns the OPeNDAP type name of the class instance as a <code>String</code>.
        /// 
        /// </summary>
        /// <returns> the OPeNDAP type name of the class instance as a <code>String</code>.
        /// </returns>
        public override String TypeName {
            get { return "Structure"; }
        }

        /// <summary> Return an Enumeration that can be used to iterate over the members of
        /// a Structure. This implementation provides access to the elements of
        /// the Structure. Each Object returned by the Enumeration can be cast to
        /// a BaseType.
        /// 
        /// </summary>
        /// <returns> An Enumeration
        /// </returns>
        public override IEnumerator Variables {
            get { return vars.GetEnumerator(); }
        }

        /// <summary> The variables in this <code>DStructure</code>, stored in a
        /// <code>Vector</code> of <code>BaseType</code> objects.
        /// </summary>
        protected internal ArrayList vars;

        /// <summary> Constructs a new <code>DStructure</code>.</summary>
        public DStructure() : this("") {}

        /// <summary> Constructs a new <code>DStructure</code> with name <code>n</code>.
        /// 
        /// </summary>
        /// <param name="n">the name of the variable.
        /// </param>
        public DStructure(String n) : base(n) {
            vars = ArrayList.Synchronized(new ArrayList(10));
        }

        /// <summary> Returns a clone of this <code>DSequence</code>.  A deep copy is performed
        /// on all data inside the variable.
        /// 
        /// </summary>
        /// <returns> a clone of this <code>DSequence</code>.
        /// </returns>
        public override Object Clone() {
            DStructure s = (DStructure) base.Clone();
            s.vars = ArrayList.Synchronized(new ArrayList(10));
            for (int i = 0; i < vars.Count; i++) {
                BaseType bt = (BaseType) vars[i];
                s.vars.Add(bt.Clone());
            }
            return s;
        }

        /// <summary> Returns the number of variables contained in this object. For simple and
        /// vector type variables, it always returns 1. To count the number
        /// of simple-type variable in the variable tree rooted at this variable, set
        /// <code>leaves</code> to <code>true</code>.
        /// 
        /// </summary>
        /// <param name="leaves">If true, count all the simple types in the `tree' of
        /// variables rooted at this variable.
        /// </param>
        /// <returns> the number of contained variables.
        /// </returns>
        public override int ElementCount(bool leaves) {
            if (!leaves)
                return vars.Count;
            else {
                int count = 0;
                //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
                for (IEnumerator e = vars.GetEnumerator(); e.MoveNext();) {
                    //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                    BaseType bt = (BaseType) e.Current;
                    count += bt.ElementCount(leaves);
                }
                return count;
            }
        }

        /// <summary> Adds a variable to the container.
        /// 
        /// </summary>
        /// <param name="v">   the variable to add.
        /// </param>
        /// <param name="part">ignored for <code>DSequence</code>.
        /// </param>
        public override void addVariable(BaseType v, int part) {
            v.Parent = this;
            vars.Add(v);
        }

        /// <summary> Returns the named variable.
        /// 
        /// </summary>
        /// <param name="name">the name of the variable.
        /// </param>
        /// <returns> the named variable.
        /// </returns>
        /// <throws>  NoSuchVariableException if the named variable does not </throws>
        /// <summary>                                 exist in this container.
        /// </summary>
        public override BaseType getVariable(String name) {
            int dotIndex = name.IndexOf('.');
            if (dotIndex != - 1) {
                // name contains "."
                String aggregate = name.Substring(0, (dotIndex) - (0));
                String field = name.Substring(dotIndex + 1);

                BaseType aggRef = getVariable(aggregate);
                if (aggRef is DConstructor)
                    return ((DConstructor) aggRef).getVariable(field);
                    // recurse
                else {} // fall through to throw statement
            }
            else {
                //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
                for (IEnumerator e = vars.GetEnumerator(); e.MoveNext();) {
                    //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                    BaseType v = (BaseType) e.Current;
                    if (v.Name.Equals(name))
                        return v;
                }
            }
            throw new NoSuchVariableException("DStructure: getVariable()");
        }


        /// <summary> Gets the indexed variable. For a DStructure this returns the
        /// <code>BaseType</code> from the <code>index</code>th column from the
        /// internal storage <code>Vector</code>.
        /// 
        /// </summary>
        /// <param name="index">the index of the variable in the <code>Vector</code> Vars.
        /// </param>
        /// <returns> the indexed variable.
        /// </returns>
        /// <throws>  NoSuchVariableException if the named variable does not </throws>
        /// <summary>                                 exist in this container.
        /// </summary>
        public override BaseType getVar(int index) {
            if (index < vars.Count)
                return ((BaseType) vars[index]);
            else
                throw new NoSuchVariableException("DStructure.getVariable(" + index + " - 1)");
        }

        /// <summary> Checks for internal consistency. For <code>DStructure</code>, verify
        /// that the variables have unique names.
        /// 
        /// </summary>
        /// <param name="all">for complex constructor types, this flag indicates whether to
        /// check the semantics of the member variables, too.
        /// </param>
        /// <throws>  BadSemanticsException if semantics are bad, explains why. </throws>
        /// <seealso cref="BaseType.CheckSemantics(bool)">
        /// </seealso>
        public override void CheckSemantics(bool all) {
            base.CheckSemantics(all);

            Utilities.UniqueNames(vars, Name, TypeName);

            if (all) {
                //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
                for (IEnumerator e = vars.GetEnumerator(); e.MoveNext();) {
                    //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                    BaseType bt = (BaseType) e.Current;
                    bt.CheckSemantics(true);
                }
            }
        }


        /// <summary> Write the variable's declaration in a C-style syntax. This
        /// function is used to create textual representation of the Data
        /// Descriptor Structure (DDS).  See <em>The OPeNDAP User Manual</em> for
        /// information about this structure.
        /// 
        /// </summary>
        /// <param name="os">        The <code>StreamWriter</code> on which to Print the
        /// declaration.
        /// </param>
        /// <param name="space">     Each line of the declaration will begin with the
        /// characters in this string.  Usually used for leading spaces.
        /// </param>
        /// <param name="print_semi">a bool value indicating whether to Print a
        /// semicolon at the end of the declaration.
        /// </param>
        /// <param name="constrained"></param>
        /// <seealso cref="BaseType.PrintDeclaration(StreamWriter,string,bool)">
        /// </seealso>
        public override void PrintDeclaration(StreamWriter os, String space, bool print_semi, bool constrained) {
            // BEWARE! Since PrintDeclaration()is (multiple) overloaded in BaseType and
            // all of the different signatures of PrintDeclaration() in BaseType lead to
            // one signature, we must be careful to override that SAME signature
            // here. That way all calls to PrintDeclaration() for this object lead to
            // this implementation.


            //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
            os.WriteLine(space + TypeName + " {");
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            for (IEnumerator e = vars.GetEnumerator(); e.MoveNext();) {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                BaseType bt = (BaseType) e.Current;
                bt.PrintDeclaration(os, space + "    ", true, constrained);
            }
            os.Write(space + "} " + Name);

            if (print_semi) {
                //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
                os.WriteLine(";");
            }
        }

        /// <summary> Prints the value of the variable, with its declaration.  This
        /// function is primarily intended for debugging OPeNDAP applications and
        /// text-based clients such as geturl.
        /// 
        /// </summary>
        /// <param name="os">          the <code>StreamWriter</code> on which to Print the value.
        /// </param>
        /// <param name="space">       this value is passed to the <code>PrintDeclaration</code> method,
        /// and controls the leading spaces of the output.
        /// </param>
        /// <param name="print_decl_p">a bool value controlling whether the
        /// variable declaration is printed as well as the value.
        /// </param>
        /// <seealso cref="BaseType.PrintValue(StreamWriter,string,bool)">
        /// </seealso>
        public override void PrintValue(StreamWriter os, String space, bool print_decl_p) {
            if (print_decl_p) {
                PrintDeclaration(os, space, false);
                os.Write(" = ");
            }

            os.Write("{ ");
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            for (IEnumerator e = vars.GetEnumerator(); e.MoveNext();) {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                BaseType bt = (BaseType) e.Current;
                bt.PrintValue(os, "", false);
                //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
                if (e.MoveNext())
                    os.Write(", ");
            }
            os.Write(" }");

            if (print_decl_p) {
                //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
                os.WriteLine(";");
            }
        }

        /// <summary> Reads data from a <code>DataInputStream</code>. This method is only used
        /// on the client side of the OPeNDAP client/server connection.
        /// 
        /// </summary>
        /// <param name="source">  a <code>DataInputStream</code> to Read from.
        /// </param>
        /// <param name="sv">      the <code>ServerVersion</code> returned by the server.
        /// </param>
        /// <param name="statusUI">the <code>StatusUI</code> object to use for GUI updates
        /// and user cancellation notification (may be null).
        /// </param>
        /// <throws>  EOFException      if EOF is found before the variable is completely </throws>
        /// <summary>                           deserialized.
        /// </summary>
        /// <throws>  IOException       thrown on any other InputStream exception. </throws>
        /// <throws>  DataReadException if an unexpected value was Read. </throws>
        /// <seealso cref="IClientIO.Deserialize">
        /// </seealso>
        //UPGRADE_TODO: Class 'java.io.DataInputStream' was converted to 'System.IO.BinaryReader' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioDataInputStream'"
        //UPGRADE_NOTE: Synchronized keyword was removed from method 'Deserialize'. Lock expression was added. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1027'"
        public virtual void Deserialize(BinaryReader source, ServerVersion sv, IStatusUI statusUI) {
            lock (this) {
                //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
                for (IEnumerator e = vars.GetEnumerator(); e.MoveNext();) {
                    if (statusUI != null && statusUI.UserCancelled())
                        throw new DataReadException("User cancelled");
                    //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                    IClientIO bt = (IClientIO) e.Current;
                    bt.Deserialize(source, sv, statusUI);
                }
            }
        }

        /// <summary> Writes data to a <code>DataOutputStream</code>. This method is used
        /// primarily by GUI clients which need to download OPeNDAP data, manipulate
        /// it, and then re-save it as a binary file.
        /// 
        /// </summary>
        /// <param name="sink">a <code>DataOutputStream</code> to write to.
        /// </param>
        /// <throws>  IOException thrown on any <code>OutputStream</code> </throws>
        /// <summary>                     exception.
        /// </summary>
        //UPGRADE_TODO: Class 'java.io.DataOutputStream' was converted to 'System.IO.BinaryWriter' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioDataOutputStream'"
        public virtual void Externalize(BinaryWriter sink) {
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            for (IEnumerator e = vars.GetEnumerator(); e.MoveNext();) {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                IClientIO bt = (IClientIO) e.Current;
                bt.Externalize(sink);
            }
        }
    }
}